package bancosys.tec.persist.span;

import static bancosys.tec.persist.span.SpanAwareBean.DATA_FIM;

import java.util.Map;

import jmine.tec.persist.PersistMessages;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.controller.PersistenceEnabledController;
import bancosys.tec.persist.dao.BeanNotFoundException;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.persist.persister.DefaultPersister;
import bancosys.tec.persist.validator.ValidationError;
import bancosys.tec.persist.validator.ValidationException;
import bancosys.tec.utils.date.Date;

/**
 * Especializacao de {@link DefaultPersister} para lidar com beans com vigencia
 * 
 * @author takeshi
 */
@SuppressWarnings({ "deprecation", "unchecked" })
public abstract class SpanAwarePersister extends DefaultPersister<SpanAwareBean> {

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.persist.persister.DefaultPersister#beforeInsert(bancosys.tec.persist.Persistable)
     */
    @Override
    protected void beforeInsert(SpanAwareBean bean) {
        super.beforeInsert(bean);
        SpanAwareBean span = bean;

        PersistenceEnabledController controller = span.getController();
        SpanAwareDAO dao = (SpanAwareDAO) controller.getDAOFactory().getDAO(span.getClass());

        SpanAwareBean lastSpan;
        try {
            lastSpan = dao.findLastSpan(this.getSpanKeys(bean));
        } catch (BeanNotFoundException e) {
            span.setDataFim(DATA_FIM);
            return;
        }

        if (lastSpan.getDataInicio().after(span.getDataInicio()) || lastSpan.getDataInicio().equals(span.getDataInicio())) {
            throw new ValidationException(new ValidationError("persist.validationError.invalidDate", bean));
        }

        Date dataFim = (Date) span.getDataInicio().clone();
        dataFim.addDays(-1);
        lastSpan.setDataFim(dataFim);
        span.setDataFim(DATA_FIM);
        lastSpan.getPurePersister().getSessionFactory().getCurrentSession().update(lastSpan);
    }

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.persist.persister.DefaultPersister#beforeRemove(bancosys.tec.persist.Persistable)
     */
    @Override
    protected void beforeRemove(SpanAwareBean bean) {
        super.beforeRemove(bean);
        SpanAwareBean span = bean;

        if (!span.getDataFim().equals(DATA_FIM)) {
            throw new ValidationException(new ValidationError("persist.validationError.onlyLastTermCanBeRemoved", bean));
        }

        PersistenceEnabledController controller = span.getController();
        SpanAwareDAO dao = (SpanAwareDAO) controller.getDAOFactory().getDAO(span.getClass());
        SpanAwareBean boRet;
        try {
            boRet = dao.findPreviousSpan(span, this.getSpanKeys(bean));
        } catch (BeanNotFoundException e) {
            return;
        }

        Date dataFim = (Date) span.getDataInicio().clone();
        dataFim.addDays(-1);
        dataFim = DATA_FIM;
        boRet.setDataFim(dataFim);
        boRet.getPurePersister().getSessionFactory().getCurrentSession().update(boRet);
    }

    /**
     * {@inheritDoc}
     * 
     * @see bancosys.tec.persist.persister.DefaultPersister#beforeUpdate(bancosys.tec.persist.Persistable)
     */
    @Override
    protected void beforeUpdate(SpanAwareBean bean) {
        super.beforeUpdate(bean);
        SpanAwareBean span = bean;

        if (!span.getDataFim().equals(DATA_FIM)) {
            throw new ValidationException(new ValidationError("persist.validationError.onlyLastTermCanBeUpdated", bean));
        }

        PersistenceEnabledController controller = span.getController();
        SpanAwareDAO dao = (SpanAwareDAO) controller.getDAOFactory().getDAO(span.getClass());

        Map<String, Object> keys = this.getSpanKeys(bean);
        SpanAwareBean lastSpan;
        try {
            lastSpan = dao.findLastSpan(keys);
        } catch (BeanNotFoundException e) {
            // Nenhuma vigência encontrada.
            throw new PersistenceException(PersistMessages.TERM_NOT_FOUND.create(), e);
        }
        SpanAwareBean boRet;
        try {
            boRet = dao.findPreviousSpan(span, keys);
        } catch (BeanNotFoundException e) {
            boRet = null;
        }

        if (span.getId().longValue() != lastSpan.getId().longValue()) {
            // o id sendo alterado NAO FOI o ultimo incluido
            // como nao é o ultimo registro incluido, nao pode alterar a data de inicio
            SpanAwareBean spanAnt = null;
            if (boRet == null) {
                try {
                    spanAnt = (SpanAwareBean) dao.findByPk(span.getPk());
                } catch (BeanNotFoundException e) {
                    // Nenhuma vigência encontrada.
                    throw new PersistenceException(PersistMessages.TERM_NOT_FOUND.create(), e);
                }
                if (!span.getDataInicio().equals(spanAnt.getDataInicio())) {
                    throw new ValidationException(new ValidationError(PersistMessages.VALIDATION_ERROR_DATE_UPDATE_NOT_ALLOWED.create(),
                            bean));
                }
            } else {
                Date dt = (Date) boRet.getDataFim().clone();
                dt.addDays(+1);
                if (!span.getDataInicio().equals(dt)) {
                    throw new ValidationException(new ValidationError(PersistMessages.VALIDATION_ERROR_DATE_UPDATE_NOT_ALLOWED.create(),
                            bean));
                }
            }
        } else {
            // o span sendo alterado FOI o ultimo incluido - acertar data fim do registro anterior
            if (boRet != null) {
                Date dataFim = (Date) span.getDataInicio().clone();
                dataFim.addDays(-1);
                if (dataFim.before(boRet.getDataInicio())) {
                    throw new ValidationException(new ValidationError(PersistMessages.VALIDATION_ERROR_INVALID_DATE_UPDATE.create(), bean));
                }
                boRet.setDataFim(dataFim);
                boRet.getPurePersister().getSessionFactory().getCurrentSession().update(boRet);
            }
        }
    }

    /**
     * Extrai criterios de busca em um mapa. Este metodo nao deveria devolver null.
     * 
     * @param bean o Persistable
     * @return {@link Map} de {@link String} para {@link Object}
     */
    public abstract Map<String, Object> getSpanKeys(Persistable bean);
}
